#include <QtGui>
#include <QtSql>
#include <QtNetwork>

#include "chatdialog.h"
#include "logdlg.h"

static const QString LogPath = QObject::tr("Log.db");

using namespace DataLogger;

ChatDialog::ChatDialog(QWidget *parent)
    : QDialog(parent)
{
    setupUi(this);

    initializeMember();
    initInputFrame();
    initStateMachine();
    initDataLogger();
    fillBlackList();
    connectSignalsAndSlots();
}

void ChatDialog::resizeEvent(QResizeEvent *event)
{
    if(!event->oldSize().isValid() || !event->size().isValid())
        return;

    const QSize discrepancy = event->size() - event->oldSize();

    {
        QRect newGeometry = tabWidget->geometry();

        newGeometry.setSize(tabWidget->size() + discrepancy);
        tabWidget->setGeometry(newGeometry);
    }

    {
        QRect newGeometry = listWidget->geometry();

        newGeometry.moveLeft(newGeometry.left() + discrepancy.width());
        newGeometry.setHeight(listWidget->height() + discrepancy.height());
        listWidget->setGeometry(newGeometry);
    }

    {
        QRect newGeometry = typeFrame->geometry();

        newGeometry.moveTop(newGeometry.top() + discrepancy.height());
        newGeometry.setWidth(tabWidget->width());
        typeFrame->setGeometry(newGeometry);
    }

    {
        QRect newGeometry = handWritingFrame->geometry();

        newGeometry.moveTop(newGeometry.top() + discrepancy.height());
        newGeometry.setWidth(tabWidget->width());
        handWritingFrame->setGeometry(newGeometry);
    }

    updateStates();
}

void ChatDialog::appendMessage(Connection::DataScope scope, const QString &from, const QVariant &message)
{
    if (from.isEmpty() || !message.isValid())
        return;

    const QTextEdit *currTextEdit = NULL;               // 当前的TextEdit
    QString fromClient, toClient;                       // 记录入Log的from和to

    /* 根据scope计算得currTextEdit、fromClient、toClient */
    switch(scope)
    {
    case Connection::Public:
        currTextEdit = textEdit;
        fromClient = from;
        toClient = publicChatLable;
        break;
    case Connection::Private:
        currTextEdit = textEditFromTitle(from);
        fromClient = from;
        toClient = thisAlias;
        break;
    default:
        currTextEdit = currentTextEdit();
        fromClient = thisAlias;
        toClient = tabWidget->tabText(tabWidget->currentIndex());
    }

    if (currTextEdit)
    {
        appendMessage(*currTextEdit, from, message);

        QScrollBar *bar = currTextEdit->verticalScrollBar();
        bar->setValue(bar->maximum());

        Connection::DataScope logScope = scope;

        if (logScope == Connection::Unknown)
        {
            if (toClient == publicChatLable)
                logScope = Connection::Public;
            else
                logScope = Connection::Private;
        }

        writeLog(logScope, fromClient, toClient, message);
    }
}

void ChatDialog::sendMessage(const QVariant &mesg)
{
    QTextEdit *currTextEdit = currentTextEdit();

    if (currTextEdit)
    {

        if (currTextEdit == textEdit)
            client.sendMessage(mesg);
        else
            client.sendMessage(currentAddress(), mesg);

        appendMessage(Connection::Unknown, myNickName, mesg);
    }
}

void ChatDialog::newParticipant(const QString &nick, const QHostAddress &addr)
{
    if (nick.isEmpty())
        return;

    QColor color = textEdit->textColor();
    textEdit->setTextColor(Qt::gray);
    textEdit->append(tr("* %1 加入").arg(nick));
    textEdit->setTextColor(color);

    QListWidgetItem *item = new QListWidgetItem();
    item->setText(nick);
    item->setData(Qt::UserRole, addr.toString());
    listWidget->addItem(item);
}

void ChatDialog::participantLeft(const QString &nick)
{
    if (nick.isEmpty())
        return;

    QList<QListWidgetItem *> items = listWidget->findItems(nick, Qt::MatchExactly);

    if (!items.isEmpty())
    {
        delete items.at(0);
        QColor color = textEdit->textColor();
        textEdit->setTextColor(Qt::gray);
        textEdit->append(tr("* %1 离开").arg(nick));
        textEdit->setTextColor(color);
    }

    for (int i = 0; i < tabWidget->count(); ++i)
    {
        if (tabWidget->tabText(i) == nick)
        {
            tabWidget->removeTab(i);
            break;
        }
    }
}

void ChatDialog::showLogData()
{
    LogDlg logDialog(this);
    logDialog.exec();
}

void ChatDialog::newTab(QListWidgetItem *item)
{
    const QString addr = item->data(Qt::UserRole).toString();

    if (addrBlackList.find(addr) == addrBlackList.end())
        (void)newTab(item->text(), true);
}

QTextEdit *ChatDialog::newTab(const QString &title, bool active)
{
    for (int i = 0; i < tabWidget->count(); ++i)
    {
        if (tabWidget->tabText(i) == title)
            return NULL;
    }

    QTextEdit *tabCentralWidget = new QTextEdit(tabWidget);
    int index = tabWidget->addTab(tabCentralWidget, title);
    if (active) tabWidget->setCurrentIndex(index);

    return tabCentralWidget;
}

/*!
  初始化输入面板
 */
void ChatDialog::initInputFrame()
{
    /* 创建并初始化打字输入面板 */
    {
        typeFrame = new TypeFrame(this);
        QRect newGeometry = typeFrame->geometry();
        newGeometry.setWidth(tabWidget->width());
        typeFrame->setGeometry(newGeometry);
    }

    /* 创建并初始化手写输入面板 */
    {
        handWritingFrame = new HandWritingFrame(this);
        QRect newGeometry = handWritingFrame->geometry();
        newGeometry.setWidth(tabWidget->width());
        handWritingFrame->setGeometry(newGeometry);
    }
}

/*!
  初始化状态机
 */
void ChatDialog::initStateMachine()
{
    /* 创建状态机 */
    stateMachine = new QStateMachine(this);

    /* 创建并加入两个状态 */
    typeState = new QState(stateMachine);
    handWritingState = new QState(stateMachine);

    /* 初始化状态 */
    initStates();

    /* 为状态转换添加触发事件并设定转换动画 */
    QAbstractTransition *transToHandWriting =
            typeState->addTransition(typeFrame->changeToHandWriting(), SIGNAL(clicked()), handWritingState);

    transToHandWriting->addAnimation(new QPropertyAnimation(typeFrame, "pos"));
    transToHandWriting->addAnimation(new QPropertyAnimation(handWritingFrame, "pos"));
    transToHandWriting->addAnimation(new QPropertyAnimation(tabWidget, "geometry"));

    QAbstractTransition *transToType =
            handWritingState->addTransition(handWritingFrame->changeToType(), SIGNAL(clicked()), typeState);

    transToType->addAnimation(new QPropertyAnimation(typeFrame, "pos"));
    transToType->addAnimation(new QPropertyAnimation(handWritingFrame, "pos"));
    transToType->addAnimation(new QPropertyAnimation(tabWidget, "geometry"));

    /* 设置初始状态并启动状态机 */
    stateMachine->setInitialState(typeState);
    stateMachine->start();
}

/*!
  初始化数据记录器，使用Sqlite数据库
 */
void ChatDialog::initDataLogger()
{
    static const QString LogTable = tr(
                "Create  TABLE IF NOT EXISTS [LogTable]("
                "[scope] integer,"
                "[from] nvarchar(30),"
                "[from_port] nvarchar(5),"
                "[to] nvarchar(30),"
                "[to_port] nvarchar(5),"
                "[data] blob,"
                "[datestamp] date,"
                "[timestamp] time)");

    bool success = false;

    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");

    if (db.isValid())
    {
        db.setDatabaseName(LogPath);
        success = db.open();
    }

    QSqlQuery query(db);

    success = success && query.exec(LogTable);

    if (!success)
    {
        qDebug() << LogTable;
        QMessageBox::critical(this, tr("错误"), tr("数据库初始化失败：%1").arg(query.lastError().text()));
    }
}

/*!
  初始化成员
 */
void ChatDialog::initializeMember()
{
    textEdit = new QTextEdit(tabWidget);
    textEdit->setFocusPolicy(Qt::NoFocus);
    textEdit->setReadOnly(true);

    publicChatLable = tr("公共聊天");
    tabWidget->addTab(textEdit, publicChatLable);

    QIcon icon;
    icon.addFile(QString::fromUtf8(":/image/image/page_edit.png"));
    cornerButton = new QPushButton(icon, QString(), this);
    cornerButton->setMinimumSize(20, 20);
    cornerButton->setMaximumSize(20, 20);
    cornerButton->setToolTip(tr("聊天记录"));
    cornerButton->setFlat(true);
    tabWidget->setCornerWidget(cornerButton);
    listWidget->setFocusPolicy(Qt::NoFocus);

    myNickName = client.nickName();
    newParticipant(myNickName, QHostAddress(QHostAddress::LocalHost));
    tableFormat.setBorder(0);
    tableFormat.setCellSpacing(3);
}

/*!
  填充通信黑名单，防止自我私聊
 */
void ChatDialog::fillBlackList()
{
    foreach (QNetworkInterface interface, QNetworkInterface::allInterfaces())
        foreach (QNetworkAddressEntry entry, interface.addressEntries())
            addrBlackList.insert(entry.ip().toString());
}

/*!
  连接状态和槽
 */
void ChatDialog::connectSignalsAndSlots()
{
    connect(cornerButton,       SIGNAL(clicked()),
            this,               SLOT(showLogData()));
    connect(typeFrame,          SIGNAL(sentMessage(QVariant)),
            this,               SLOT(sendMessage(QVariant)));
    connect(handWritingFrame,   SIGNAL(sendMessage(QVariant)),
            this,               SLOT(sendMessage(QVariant)));
    connect(listWidget,         SIGNAL(itemDoubleClicked(QListWidgetItem*)),
            this,               SLOT(newTab(QListWidgetItem*)));
    connect(&client,            SIGNAL(newMessage(Connection::DataScope,QString,QVariant)),
            this,               SLOT(appendMessage(Connection::DataScope,QString,QVariant)));
    connect(&client,            SIGNAL(newParticipant(QString,QHostAddress)),
            this,               SLOT(newParticipant(QString,QHostAddress)));
    connect(&client,            SIGNAL(participantLeft(QString)),
            this,               SLOT(participantLeft(QString)));
}

/*!
  返回当前Tab页面中的TextEdit

  \return 当前的TextEdit
 */
QTextEdit *ChatDialog::currentTextEdit() const
{
    QTextEdit *currTextEdit = qobject_cast<QTextEdit *>(tabWidget->currentWidget());

    Q_ASSERT(currTextEdit);
    return currTextEdit;
}

/*!
  根据Tab页面的Title返回TextEdit，如果没有，创建一个具有Title页面的Tab

  \param title Tab页面的title
  \return title页面的TextEdit
 */
QTextEdit *ChatDialog::textEditFromTitle(const QString &title)
{
    QTextEdit *retTextEdit = NULL;

    for (int i = 0; i < tabWidget->count(); ++i)
    {
        if (tabWidget->tabText(i) == title)
        {
            retTextEdit = qobject_cast<QTextEdit *>(tabWidget->widget(i));

            Q_ASSERT(retTextEdit);
            break;
        }
    }

    if (!retTextEdit) return newTab(title, false);

    return retTextEdit;
}

/*!
  返回当前Tab页面对应的QHostAddress

  \return 当前的QHostAddress
 */
QHostAddress ChatDialog::currentAddress() const
{
    QHostAddress currAddr;

    int currTabIndex = tabWidget->currentIndex();

    if (currTabIndex != -1)
    {
        const QString currTabTitle = tabWidget->tabText(currTabIndex);
        QList<QListWidgetItem *> currListItems = listWidget->findItems(currTabTitle, Qt::MatchExactly);

        if (!currListItems.isEmpty())
        {
            const QVariant currData = currListItems.first()->data(Qt::UserRole);
            currAddr.setAddress(currData.toString());

            Q_ASSERT(!currAddr.isNull());
        }
    }

    return currAddr;
}

/*!
  从currTextEdit中找到插入点游标

  \param currTextEdit 需要寻找插入点游标的QTextEdit
  \param from 消息发送者
  \param message 发送的消息
 */
void ChatDialog::appendMessage(const QTextEdit &currTextEdit, const QString &from, const QVariant &message) const
{
    const QVariant::Type mesgType = message.type();     // 消息类型
    QTextCursor mesgCursor;                             // 消息插入位置游标

    /* 插入from信息，并更新消息插入点游标到mesgCursor */
    {
        QTextCursor cursor(currTextEdit.textCursor());
        cursor.movePosition(QTextCursor::End);

        QTextCharFormat format;
        format.setForeground(Qt::darkGreen);

        QTextTable *table = cursor.insertTable(2, 1, tableFormat);
        table->cellAt(0, 0).firstCursorPosition().insertText('<' + from + "> ", format);
        table->cellAt(0, 0).lastCursorPosition().insertText(QTime::currentTime().toString(), format);

        mesgCursor = table->cellAt(1, 0).firstCursorPosition();
    }

    if (mesgType == QVariant::String)
        mesgCursor.insertText(message.toString());
    else if (mesgType == QVariant::Image)
        mesgCursor.insertImage(message.value<QImage>());
}

/*!
  插入一条Log记录

  \param scope 消息域
  \param from 消息来源
  \param to 消息去向
  \param data 消息内容
 */
void ChatDialog::writeLog(Connection::DataScope scope, const QString &from, const QString &to, const QVariant &data)
{
    static const QString LogData = tr(
                "INSERT INTO [LogTable]"
                "([scope], [from], [from_port], [to], [to_port], [data], [datestamp], [timestamp])"
                "VALUES"
                "(:scope, :from, :from_port, :to, :to_port, :data, :datestamp, :timestamp)");

    QSqlDatabase db = QSqlDatabase::database();
    QSqlQuery query(db);

    if (query.prepare(LogData))
    {
        QByteArray writeData;
        QDataStream writeStream(&writeData, QIODevice::WriteOnly);

        writeStream << data;

        QStringList fromName = from.split(':');
        QStringList toName = to.split(":");

        /* 修补“公共聊天”、“本机” */
        if (fromName.size() == 1) fromName.append("");
        if (toName.size() == 1) toName.append("");

        Q_ASSERT(fromName.size() == 2 && toName.size() == 2);
        Q_ASSERT(fromName.at(1).size() <= 5 && toName.at(1).size() <= 5);

        query.bindValue(":scope", scope);
        query.bindValue(":from", fromName.at(0));
        query.bindValue(":from_port", fromName.at(1));
        query.bindValue(":to", toName.at(0));
        query.bindValue(":to_port", toName.at(1));
        query.bindValue(":data", writeData);
        query.bindValue(":datestamp", QDate::currentDate().toString(Qt::ISODate));
        query.bindValue(":timestamp", QTime::currentTime().toString(Qt::ISODate));

        query.exec();

        qDebug() << query.lastError().text();
    }
}

/*!
  初始化状态，主要在状态机初始化函数（initStateMachine）中调用
 */
void ChatDialog::initStates()
{
    /* 初始化打字输入模式状态 */
    {
        QRect newGeometry(tabWidget->geometry());
        newGeometry.setHeight(tabWidget->height() - typeFrame->height());
        typeState->assignProperty(tabWidget, "geometry", newGeometry);
        typeState->assignProperty(typeFrame, "pos", newGeometry.bottomLeft() + QPoint(0, 2));
        typeState->assignProperty(typeFrame, "enabled", true);
        typeState->assignProperty(handWritingFrame, "pos", newGeometry.bottomLeft() + QPoint(0, typeFrame->height() + 5));
        typeState->assignProperty(handWritingFrame, "enabled", false);
    }

    /* 初始化手写输入模式状态 */
    {
        QRect newGeometry(tabWidget->geometry());
        newGeometry.setHeight(tabWidget->height() - handWritingFrame->height());
        handWritingState->assignProperty(tabWidget, "geometry", newGeometry);
        handWritingState->assignProperty(handWritingFrame, "pos", newGeometry.bottomLeft() + QPoint(0, 2));
        handWritingState->assignProperty(handWritingFrame, "enabled", true);
        handWritingState->assignProperty(typeFrame, "pos", newGeometry.bottomLeft() + QPoint(0, handWritingFrame->height() + 5));
        handWritingState->assignProperty(typeFrame, "enabled", false);
    }
}

/*!
  更新状态，主要在窗体resize时调用
 */
void ChatDialog::updateStates()
{
    /* 希望QStateMachine能有currentState()这样的函数 */
    QState *currentState = typeFrame->isEnabled() ? typeState : handWritingState;

    /* 直接更新当前状态 */
    currentState->assignProperty(tabWidget, "geometry", tabWidget->geometry());
    currentState->assignProperty(typeFrame, "pos", typeFrame->pos());
    currentState->assignProperty(handWritingFrame, "pos", handWritingFrame->pos());

    /* 根据当前状态计算另一状态 */
    if(currentState == typeState)
    {
        QRect newGeometry = tabWidget->geometry();
        newGeometry.setHeight(
                    tabWidget->height() + typeFrame->height() - handWritingFrame->height());
        handWritingState->assignProperty(
                    tabWidget, "geometry", newGeometry);
        handWritingState->assignProperty(
                    handWritingFrame, "pos", newGeometry.bottomLeft() + QPoint(0, 2));
        handWritingState->assignProperty(
                    typeFrame, "pos", newGeometry.bottomLeft() + QPoint(0, handWritingFrame->height() + 5));
    }
    else
    {
        QRect newGeometry = tabWidget->geometry();
        newGeometry.setHeight(
                    tabWidget->height() + handWritingFrame->height() - typeFrame->height());
        typeState->assignProperty(
                    tabWidget, "geometry", newGeometry);
        typeState->assignProperty(
                    typeFrame, "pos", newGeometry.bottomLeft() + QPoint(0, 2));
        typeState->assignProperty(
                    handWritingFrame, "pos", newGeometry.bottomLeft() + QPoint(0, typeFrame->height() + 5));
    }
}
